package com.videoweb.service;

import com.videoweb.mapper.ChunkInfoMapper;
import com.videoweb.mapper.UploadStatusMapper;
import com.videoweb.mapper.VideoMapper;
import com.videoweb.model.ChunkInfo;
import com.videoweb.model.UploadStatus;
import com.videoweb.model.Video;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;

@Slf4j
@Service
public class ChunkedUploadService {
    
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ChunkedUploadService.class);
    
    @Autowired
    private UploadStatusMapper uploadStatusMapper;
    
    @Autowired
    private ChunkInfoMapper chunkInfoMapper;
    
    @Autowired
    private VideoMapper videoMapper;
    
    @Value("${app.upload.temp-dir:./temp}")
    private String tempDir;
    
    @Value("${app.upload.final-dir:./uploads}")
    private String finalDir;
    
    @Value("${app.upload.cleanup-hours:24}")
    private int cleanupHours;
    
    /**
     * 获取上传状态
     */
    public UploadStatus getUploadStatus(String fileHash) {
        return uploadStatusMapper.findByFileHash(fileHash);
    }
    
    /**
     * 创建或获取上传状态
     */
    @Transactional
    public UploadStatus getOrCreateUploadStatus(String fileHash, String originalFilename, 
                                              Long fileSize, Integer totalChunks, 
                                              Integer chunkSize, Long userId, 
                                              String title, String description) {
        UploadStatus uploadStatus = uploadStatusMapper.findByFileHash(fileHash);
        
        if (uploadStatus == null) {
            // 创建新的上传状态
            uploadStatus = new UploadStatus();
            uploadStatus.setFileHash(fileHash);
            uploadStatus.setOriginalFilename(originalFilename);
            uploadStatus.setFileSize(fileSize);
            uploadStatus.setTotalChunks(totalChunks);
            uploadStatus.setChunkSize(chunkSize);
            uploadStatus.setUserId(userId);
            uploadStatus.setTitle(title);
            uploadStatus.setDescription(description);
            uploadStatus.setStatus(UploadStatus.UploadStatusEnum.UPLOADING);
            
            // 创建临时目录
            String uploadDir = createTempDir(fileHash);
            uploadStatus.setUploadDir(uploadDir);
            
            uploadStatusMapper.insert(uploadStatus);
            
            log.info("创建新的上传状态: fileHash={}, filename={}, size={}, chunks={}", 
                fileHash, originalFilename, fileSize, totalChunks);
        } else {
            // 更新已有状态的用户信息和标题描述
            if (userId != null) {
                uploadStatus.setUserId(userId);
            }
            if (title != null && !title.trim().isEmpty()) {
                uploadStatus.setTitle(title);
            }
            if (description != null && !description.trim().isEmpty()) {
                uploadStatus.setDescription(description);
            }
            uploadStatusMapper.updateById(uploadStatus);
            
            log.info("使用已有上传状态: fileHash={}, 已上传分片={}/{}", 
                fileHash, uploadStatus.getUploadedChunkSet().size(), totalChunks);
        }
        
        return uploadStatus;
    }
    
    /**
     * 保存分片文件
     */
    @Transactional
    public void saveChunk(UploadStatus uploadStatus, Integer chunkIndex, MultipartFile chunk) throws IOException {
        String fileHash = uploadStatus.getFileHash();
        
        // 检查分片是否已存在
        ChunkInfo existingChunk = chunkInfoMapper.findByFileHashAndChunkIndex(fileHash, chunkIndex);
        if (existingChunk != null) {
            log.info("分片已存在，跳过保存: fileHash={}, chunkIndex={}", fileHash, chunkIndex);
            return;
        }
        
        // 确保临时目录存在
        Path tempDirPath = Paths.get(uploadStatus.getUploadDir());
        if (!Files.exists(tempDirPath)) {
            Files.createDirectories(tempDirPath);
        }
        
        // 保存分片文件
        String chunkFileName = String.format("chunk_%d", chunkIndex);
        Path chunkFilePath = tempDirPath.resolve(chunkFileName);
        
        try (InputStream inputStream = chunk.getInputStream()) {
            Files.copy(inputStream, chunkFilePath, StandardCopyOption.REPLACE_EXISTING);
        }
        
        // 计算分片MD5
        String chunkHash = calculateFileMD5(chunkFilePath.toFile());
        
        // 保存分片信息到数据库
        ChunkInfo chunkInfo = new ChunkInfo();
        chunkInfo.setFileHash(fileHash);
        chunkInfo.setChunkIndex(chunkIndex);
        chunkInfo.setChunkSize((int) chunk.getSize());
        chunkInfo.setChunkHash(chunkHash);
        chunkInfo.setFilePath(chunkFilePath.toString());
        
        chunkInfoMapper.insert(chunkInfo);
        
        log.info("分片保存成功: fileHash={}, chunkIndex={}, size={}, path={}", 
            fileHash, chunkIndex, chunk.getSize(), chunkFilePath);
    }
    
    /**
     * 标记分片已上传
     */
    @Transactional
    public void markChunkUploaded(String fileHash, Integer chunkIndex) {
        UploadStatus uploadStatus = uploadStatusMapper.findByFileHash(fileHash);
        if (uploadStatus != null) {
            uploadStatus.addUploadedChunk(chunkIndex);
            uploadStatusMapper.updateUploadedChunks(
                fileHash, 
                uploadStatus.getUploadedChunks(), 
                LocalDateTime.now()
            );
            
            log.info("标记分片已上传: fileHash={}, chunkIndex={}, progress={}/{}", 
                fileHash, chunkIndex, uploadStatus.getUploadedChunkSet().size(), uploadStatus.getTotalChunks());
        }
    }
    
    /**
     * 合并分片文件
     */
    @Transactional
    public String mergeChunks(UploadStatus uploadStatus) throws IOException {
        String fileHash = uploadStatus.getFileHash();
        
        // 更新状态为合并中
        uploadStatusMapper.updateStatus(fileHash, UploadStatus.UploadStatusEnum.MERGING.getCode(), LocalDateTime.now());
        
        try {
            // 验证所有分片是否存在
            List<ChunkInfo> chunks = chunkInfoMapper.findByFileHash(fileHash);
            if (chunks.size() != uploadStatus.getTotalChunks()) {
                throw new RuntimeException(String.format("分片数量不匹配: 期望%d个，实际%d个", 
                    uploadStatus.getTotalChunks(), chunks.size()));
            }
            
            // 按索引排序
            chunks.sort((a, b) -> Integer.compare(a.getChunkIndex(), b.getChunkIndex()));
            
            // 创建最终文件路径
            String finalFileName = generateFinalFileName(uploadStatus.getOriginalFilename());
            Path finalFilePath = Paths.get(finalDir, finalFileName);
            
            // 确保目标目录存在
            Files.createDirectories(finalFilePath.getParent());
            
            // 合并分片
            log.info("开始合并分片: fileHash={}, 目标文件={}", fileHash, finalFilePath);
            
            try (FileOutputStream fos = new FileOutputStream(finalFilePath.toFile())) {
                for (ChunkInfo chunk : chunks) {
                    Path chunkFilePath = Paths.get(chunk.getFilePath());
                    if (!Files.exists(chunkFilePath)) {
                        throw new RuntimeException("分片文件不存在: " + chunkFilePath);
                    }
                    
                    try (FileInputStream fis = new FileInputStream(chunkFilePath.toFile())) {
                        byte[] buffer = new byte[8192]; // 8KB buffer
                        int bytesRead;
                        while ((bytesRead = fis.read(buffer)) != -1) {
                            fos.write(buffer, 0, bytesRead);
                        }
                    }
                }
            }
            
            // 验证合并后文件大小
            long mergedFileSize = Files.size(finalFilePath);
            if (mergedFileSize != uploadStatus.getFileSize()) {
                throw new RuntimeException(String.format("合并后文件大小不匹配: 期望%d字节，实际%d字节", 
                    uploadStatus.getFileSize(), mergedFileSize));
            }
            
            // 验证合并后文件MD5
            String mergedFileMD5 = calculateFileMD5(finalFilePath.toFile());
            if (!mergedFileMD5.equals(fileHash)) {
                throw new RuntimeException("合并后文件MD5校验失败");
            }
            
            // 更新上传状态
            uploadStatusMapper.updateFinalPath(
                fileHash, 
                finalFileName, 
                UploadStatus.UploadStatusEnum.COMPLETED.getCode(), 
                LocalDateTime.now()
            );
            
            log.info("文件合并成功: fileHash={}, finalPath={}, size={}", 
                fileHash, finalFileName, mergedFileSize);
            
            return finalFileName;
            
        } catch (Exception e) {
            // 合并失败，更新状态
            uploadStatusMapper.updateStatus(fileHash, UploadStatus.UploadStatusEnum.FAILED.getCode(), LocalDateTime.now());
            log.error("文件合并失败: fileHash={}, error={}", fileHash, e.getMessage(), e);
            throw e;
        }
    }
    
    /**
     * 创建视频记录
     */
    @Transactional
    public Video createVideoRecord(UploadStatus uploadStatus) {
        Video video = new Video();
        video.setTitle(uploadStatus.getTitle());
        video.setDescription(uploadStatus.getDescription());
        video.setFilePath(uploadStatus.getFinalPath());
        video.setFileSize(uploadStatus.getFileSize());
        video.setUserId(uploadStatus.getUserId());
        
        videoMapper.insert(video);
        
        log.info("创建视频记录成功: videoId={}, title={}, filePath={}", 
            video.getId(), video.getTitle(), video.getFilePath());
        
        return video;
    }
    
    // 复杂的合并方法暂时移除，待修复后重新添加

    /**
     * 清理临时文件
     */
    @Transactional
    public void cleanupTempFiles(String fileHash) {
        try {
            UploadStatus uploadStatus = uploadStatusMapper.findByFileHash(fileHash);
            if (uploadStatus != null && uploadStatus.getUploadDir() != null) {
                Path tempDirPath = Paths.get(uploadStatus.getUploadDir());
                if (Files.exists(tempDirPath)) {
                    deleteDirectory(tempDirPath.toFile());
                    log.info("清理临时目录成功: {}", tempDirPath);
                }
            }
            
            // 删除分片信息
            chunkInfoMapper.deleteByFileHash(fileHash);
            
        } catch (Exception e) {
            log.error("清理临时文件失败: fileHash={}, error={}", fileHash, e.getMessage(), e);
        }
    }
    
    /**
     * 清理超时的上传任务
     */
    @Transactional
    public void cleanupTimeoutUploads() {
        LocalDateTime timeoutTime = LocalDateTime.now().minusHours(cleanupHours);
        
        try {
            List<UploadStatus> timeoutUploads = uploadStatusMapper.findTimeoutUploads(timeoutTime);
            
            for (UploadStatus upload : timeoutUploads) {
                cleanupTempFiles(upload.getFileHash());
                log.info("清理超时上传任务: fileHash={}, created={}", 
                    upload.getFileHash(), upload.getCreatedAt());
            }
            
            // 删除超时记录
            int deletedCount = uploadStatusMapper.deleteTimeoutUploads(timeoutTime);
            log.info("清理超时上传记录: 删除{}条记录", deletedCount);
            
        } catch (Exception e) {
            log.error("清理超时上传任务失败", e);
        }
    }
    
    // ==================== 私有工具方法 ====================
    
    /**
     * 创建临时目录
     */
    private String createTempDir(String fileHash) {
        try {
            Path tempPath = Paths.get(tempDir, fileHash);
            Files.createDirectories(tempPath);
            return tempPath.toString();
        } catch (IOException e) {
            throw new RuntimeException("创建临时目录失败", e);
        }
    }
    
    /**
     * 生成最终文件名
     */
    private String generateFinalFileName(String originalFilename) {
        String extension = getFileExtension(originalFilename);
        return UUID.randomUUID().toString() + extension;
    }
    
    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String filename) {
        if (filename == null || !filename.contains(".")) {
            return "";
        }
        return filename.substring(filename.lastIndexOf("."));
    }
    
    /**
     * 计算文件MD5
     */
    private String calculateFileMD5(File file) {
        try (FileInputStream fis = new FileInputStream(file)) {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            while ((bytesRead = fis.read(buffer)) != -1) {
                md.update(buffer, 0, bytesRead);
            }
            
            byte[] digest = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            
            return sb.toString();
            
        } catch (Exception e) {
            throw new RuntimeException("计算文件MD5失败", e);
        }
    }
    
    /**
     * 递归删除目录
     */
    private void deleteDirectory(File directory) {
        if (directory.exists()) {
            File[] files = directory.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        deleteDirectory(file);
                    } else {
                        file.delete();
                    }
                }
            }
            directory.delete();
        }
    }
} 